package controle;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.HashMap;

import malha.CChave;
import malha.CComposicao;
import malha.CElementoTrilho;
import malha.CMalha;
import malha.CTrecho;
import malhaFerroviaria.Ferrovia;
import interfacesFerrovia.TratadorDeEventos;



/**
 * Classe que implementa a interface TratadorDeEventos.
 * Tambem realiza a tarefa de controle do sistema. Avaliando situacoes e enviandocomandos
 */
public class CControle implements TratadorDeEventos {
	
	//Constantes
	public static final int SEC_MARGIN = 3; 
	public static final float DELTA = 1f;
	public static final float DELTA_DIST = 60f;
	
	//Atributos
	private CMalha malha;
	private Ferrovia ferrovia; 
	private int instanteAtual = -1; 
	static CControle controle = null;
	
	/**
	 * CControle singleton
	 */
	private CControle(){
		ferrovia = Ferrovia.getInstance();
		malha = new CMalha();
		
		assert(ferrovia != null);
	}
	
	/**
	 * Retorna uma instância singleton do controle
	 */
	static public CControle getControle(){
		if(controle == null){
			controle = new CControle();
		}			
		return controle; 
	}
	
	/**
	 * Reliza Bhaskara
	 * @param a Coeficiente de grau 2 
	 * @param b Coeficiente de grau 1
	 * @param c Coeficiente de grau 0
	 * @return Um vetor de duas posiçoes com as raizes da equacao
	 */
	static public float[] bhaskara(float a, float b, float c) {
		float delta = (float) Math.pow(b, 2) - 4*a*c;
		
		float r0 = -b + (float)Math.sqrt(delta)/(2*a);
		float r1 = -b - (float)Math.sqrt(delta)/(2*a);
		
		float raizes[] = {r0, r1};
	
		return raizes;
		
	}
	
	/**
	 * Calcula o tempo que uma composicao, acelerando na aceleracao maxima, levarah para 
	 * chegar ateh a extremidade do trecho que em que se encontra
	 * @param composicao Composicao cujo tempo de chegada serah calculado
	 * @return Tempo que levarah para chegar até a extremidade, em segundos.
	 */
	static public int calculaTempoAteChave(CComposicao composicao, boolean urgente){
		float s0 = composicao.getPosicaoAtual();
		float v0 = composicao.getVelAtual();
		float s = composicao.getTrechoChaveAtual().getDistancia();
		float a;
		
		if (urgente == true) {
			a = composicao.getAcelMax();
		} else if (composicao.getAcelAtual() != 0) {
			a = Math.max(composicao.getAcelAtual(), composicao.getFrenAtual());
		} else {
			a = -composicao.getFrenAtual();
		}
		
		//Ajustes de sentido da composicao
		if (composicao.isMovendoAParaB() == false) {
			s = 0f;
			a = -a;
			v0 = -v0;
		}		
		
		//Tempo ate chegar aa velocidade maxima
		int tTillMax = (int)Math.floor((composicao.getVelMax() - Math.abs(v0))/composicao.getAcelMax());
		//Distancia percorrida para chegar aa velocidade maxima
		float sTillMax = (float) (s0 + v0*tTillMax + (a*Math.pow(tTillMax, 2))/2);
		//Tempo durante velocidade maxima
		int tAtMax = (int)Math.floor(Math.abs(s -sTillMax)/composicao.getVelMax());
		return tTillMax + tAtMax;
	}
	
	/** Dada uma composicao, 
	 * calcula a aceleracao necessaria para chegar ao final do trecho no tempo dado.
	 * OBS.: Aceleracao negativa == freie, aceleracao positiva == acelere
	 * @param composicao CComposicao a ser modificada
	 * @param t Tempo no qual se deseja saber o valor da aceleracao
	 * @return A aceleracao no tempo t
	 */
	static public float calculaAceleracaoNoTempo(CComposicao composicao, int t){
		float s0 = composicao.getPosicaoAtual();
		float v0 = composicao.getVelAtual();
		float s = 0;
		if(composicao.isMovendoAParaB()){
			s = composicao.getTrechoChaveAtual().getDistancia();
		}		
		float dist = Math.abs(s - s0);
		
		float a = (2*(dist-v0*t))/(float) Math.pow(t, 2);
		
		if(v0 + a*t > composicao.getVelMax()){
			a = composicao.getAcelMax();
		}
		
		return a;
	}
	
	/**
	 * Compara dois floats representando ACELERACAO/FRENAGEM ou VELOCIDADE
	 * considerando como erro de precisao a constante DELTA definida para esse fim
	 * @param a Primeiro valor
	 * @param b Segundo valor
	 * @return True caso sejam iguais a menos de DELTA, falso caso contrario
	 */
	static public boolean floatEquals(float a, float b){
		return ((a + DELTA > b) && (a - DELTA < b) );
	}
	
	/**
	 * Metodo para realizar comparacao entre floats representando 
	 * distancia considerando como erro de precisao a constante DELTA_DIST
	 * @param a Primeiro Valor
	 * @param b Segundo Valor
	 * @return True caso sejam iguais a menos de DELTA_DIST, falso caso contrario
	 */
	static public boolean floatEqualsDist(float a, float b){
		return ((a + DELTA_DIST > b) && (a - DELTA_DIST < b) );

	}
	
	/**
	 * Calcula a velocidade em que a composicao estara depois de 
	 * acelerar com aceleracao accel durante t segundos
	 * @param composicao CComposicao sobre a qual se requer a informacao 
	 * @param t Tempo no qual se deseja descobrir a velocidade
	 * @param accel Aceleracao constante aplicada durante o tempo t
	 * @return Velocidade atingida no tempo t devida a aceleracao
	 */
	static public float calculaVelocidadeNoTempo(CComposicao composicao, int t, float accel){
		float v = composicao.getVelAtual() + accel*t;
		
		return Math.min(Math.abs(v), composicao.getVelMax());
	}
	
	/**
	 * Sinaliza falha no funcionamento de uma chave
	 * 
	 * @param idChave Chave que disparou o alarme
	 * @param instanteAtual Instante em que ocorreu o alarme
	 * 
	 * @pre idChave deve existir na malha
	 * @pre idChave deve ser a identificacao unica de uma chave
	 * @pre instanteAtual deve ser positivo
	 * 
	 * @post O evento foi tratado
	 * @post Comandos executados a fim de evitar acidentes 
	 */
	public void alarmeChave(String idChave, int instanteAtual,
			TipoFalha tipoDeFalha) {
		boolean tratado = false;
		/* Pre */
		assert(this.malha.getChaves().containsKey(idChave));
		assert(instanteAtual >= 0);
		
		System.out.println("==> alarmeChave id:"+idChave+" t:"+instanteAtual+" falha:"+tipoDeFalha);
		
		/* Atualiza elemento que gerou o alarme */
		Map<TipoAtributo, Object> atributosVariaveis = this.ferrovia.consultaEstado(idChave);
		this.malha.atualizarElemento(idChave, atributosVariaveis);
		
		this.asseguraChave(idChave);
		tratado = true;
		
		/* Post */
		assert(tratado);
	}

	
	/**
	 * Sinaliza falha no funcionamento de uma composicao
	 * 
	 * @param idComposicao Composicao que disparou o alarme
	 * @param instanteAtual Instante em que ocorreu o alarme
	 * 
	 * @pre idComposicao deve existir na malha
	 * @pre idComposicao deve ser a identificacao unica de uma composicao
	 * @pre instanteAtual deve ser positivo
	 * 
	 * @post O evento foi tratado
	 * @post Comandos executados a fim de evitar acidentes
	 */
	public void alarmeComposicao(String idComposicao, int instanteAtual,
			TipoFalha tipoDeFalha) {
		/* Pre */
		assert(this.malha.getComposicoes().containsKey(idComposicao));
		assert(instanteAtual > 0);
		
		System.out.println("==> alarmeComposicao id:"+idComposicao+" t:"+instanteAtual+" falha:"+tipoDeFalha);
		
		/* Atualiza elemento que gerou o alarme */
		Map<TipoAtributo, Object> atributosVariaveis = this.ferrovia.consultaEstado(idComposicao);
		this.malha.atualizarElemento(idComposicao, atributosVariaveis);
		
		this.asseguraChave(idComposicao);
	}

	
	/**
	 * Sinaliza a chegada de uma composicao aa seu destino
	 * 
	 * @param idComposicao Composicao que disparou o evento
	 * @param instanteAtual Instante em que ocorreu o evento
	 * @param idTrecho Trecho de destino da composicao
	 * 
	 * @pre idComposicao deve existir na malha
	 * @pre idComposicao deve ser a identificacao unica de uma composicao
	 * @pre instanteAtual deve ser positivo
	 * @pre idTrecho deve existir na malha
	 * @pre idTrecho deve ser a identificacao unica de um trecho
	 * 
	 * @post O evento foi tratado
	 * @post A composicao foi removida da malha
	 */
	public void chegadaComposicao(String idComposicao, int instanteAtual,
			String idTrecho) {
		//Pre
		assert(this.malha.getComposicoes().containsKey(idComposicao));
		assert(this.malha.getTrechos().containsKey(idTrecho));
		assert(instanteAtual > 0);
		
		System.out.println("==> Chegada de Composicao id:"+idComposicao+" t:"+instanteAtual+" trecho:"+idTrecho);
			
		this.malha.removerComposicao(idComposicao);
		
		//Post
		assert(!this.malha.getComposicoes().containsKey(idComposicao));
	}

	
	/**
	 * Sinaliza a entrada de uma composicao em uma chave
	 * 
	 * @param idChave Chave em que ocorreu o evento
	 * @param instanteAtual Instante em que ocorreu o evento
	 * @param idComposicao Composicao que disparou o evento
	 * @param idTrechoOrigem Trecho de origem da composicao que disparou o evento
	 * 
	 * @pre idChave deve existir na malha
	 * @pre idChave deve ser a identificacao unica de uma chave
	 * @pre instanteAtual deve ser positiva
	 * @pre idComposicao deve existir na malha 
	 * @pre idComposicao deve ser a identificacao unica de uma composicao
	 * @pre idTrechoOrigem deve existir na malha
	 * @pre idTrechoOrigem deve ser a identificacao unica de um trecho
	 * 
	 * @post O evento foi tratado
	 * @post Comandos executados a fim de evitar acidentes
	 */
	public void entradaComposicaoChave(String idChave, int instanteAtual,
			String idComposicao, String idTrechoOrigem) {
		//Pre
		assert(this.malha.getChaves().containsKey(idChave));
		assert(this.malha.getComposicoes().containsKey(idComposicao));
		assert(this.malha.getTrechos().containsKey(idTrechoOrigem));
		assert(instanteAtual > 0);
		
		System.out.println("==> entrada na chave id:"+idChave+" t:"+instanteAtual+
				" id.Comp.:"+idComposicao+" tr.Origem:"+idTrechoOrigem);
		
		//Realiza a transferencia do trecho para a chave
		CComposicao trem = this.malha.getComposicoes().get(idComposicao);
		CChave chave = this.malha.getChaves().get(idChave);
		CTrecho trecho = this.malha.getTrechos().get(idTrechoOrigem);
		trecho.getComposicoes().remove(trem);
		chave.setComposicaoPassando(trem);
		trecho.setnComposicoes(trecho.getnComposicoes() - 1);
		trem.setTrechoChaveAtual(chave);
		if (chave.getTrechosA().contains(idTrechoOrigem)) {
			trem.setPosicaoAtual(0f);
		} else {
			trem.setPosicaoAtual(chave.getDistancia());
		}
		
		this.malha.removerComposicaoACaminho(idChave, idComposicao);
	
		this.asseguraChave(idChave);
		
	}

	
	/**
	 * Sinaliza a partida de uma composicao rumo a� seu destino
	 * 
	 * @param idComposicao Composicao que disparou o evento
	 * @param instanteAtual Instante em que ocorreu o evento
	 * @param idTrecho Trecho de origem da composicao
	 * 
	 * @pre idComposicao deve existir na malha
	 * @pre idComposicao deve ser a identificacao unica de uma composcao
	 * @pre instanteAtual deve ser positivo
	 * @pre idTrecho deve existir na malha
	 * @pre idTrecho deve ser a identificacao unica de um trecho
	 * 
	 * @post O evento foi tratado
	 * @post Comandos executados a fim de evitar acidentes
	 */
	public void partidaComposicao(String idComposicao, int instanteAtual,
			String idTrecho) {
		//Pre
		assert(this.malha.getComposicoes().containsKey(idComposicao));
		assert(this.malha.getTrechos().containsKey(idTrecho));
		assert(instanteAtual >= 0);
		
		System.out.println("==> partida composicao id:"+idComposicao+" t:"+instanteAtual+" trecho:"+idTrecho);
		
		this.asseguraChave(idComposicao);
	}
	

	/**
	 * Sinaliza a passagem de uma composi��o por um sensor de um trecho
	 * 
	 * @param idTrecho Trecho em que ocorreu o evento
	 * @param instanteAtual Instante em que ocorreu o evento
	 * @param posicao Posic�o do trecho em que ocorreu o evento
	 * @param idComposicao Composi��o que disparou o evento
	 * @param velocidade Velocidade com que a composi��o passou no sensor
	 * @param sentidoAB Sentido em que a composi��o passou no sensor 
	 * 
	 * @pre idTrecho deve existir na malha
	 * @pre idTrecho deve ser o identificador unico de um trecho
	 * @pre instanteAtual deve ser positivo
	 * @pre posicao deve ser positivo
	 * @pre idComposicao deve existir na malha
	 * @pre idComposicao deve ser o identificador unico de uma composicao
	 * @pre velocidade deve ser positiva diferente de zero
	 * 
	 * @inv a composicao nao muda de trecho
	 * 
	 * @post O evento foi tratado
	 * @post Comandos executados a fim de evitar acidentes
	 */
	public void passagemComposicao(String idTrecho, int instanteAtual,
			float posicao, String idComposicao, float velocidade,
			boolean sentidoAB) {
		//Pre
		assert(this.malha.getTrechos().containsKey(idTrecho));
		assert(this.malha.getComposicoes().containsKey(idComposicao));
		assert(posicao >= 0);
		assert(Math.abs(velocidade) > 0);
		
		System.out.println("==> passagem composicao id:"+idComposicao+" t:"+instanteAtual+
				" trecho:"+idTrecho+" pos:"+posicao+" vel:"+velocidade+
				" sent.AB:"+sentidoAB);
		
		this.asseguraChave(idComposicao);
	}

	
	/**
	 * Sinaliza a saida de uma composicao de uma chave
	 * 
	 * @param idChave Chave da qual a composicao saiu
	 * @param instanteAtual Instante em que ocorreu o evento
	 * @param idComposicao Composicao que disparou o evento
	 * @param idTrechoDestino Trecho de destino da composicao que disparou o evento
	 * 
	 * @pre idChave deve existir na malha
	 * @pre idChave deve ser a identificacao unica de uma chave
	 * @pre instanteAtual deve ser positiva
	 * @pre idComposicao deve existir na malha
	 * @pre idComposicao deve ser a identificacao unica de uma composicao
	 * @pre idTrechoDestino deve existir na malha
	 * @pre idTrechoDestino deve ser a identificacao unica de um trecho
	 * 
	 * @post O evento foi tratado
	 * @post A composicao foi removida do Mapa trensACaminho de idChave
	 * @post A composicao foi adicionada ao Mapa trensACaminho da chave associada ao trecho de destino
	 * @post Comandos executados a fim de evitar acidentes
	 * @post A chave sera configurada para a proxima composicao aa caminho
	 */
	public void saidaComposicaoChave(String idChave, int instanteAtual,
			String idComposicao, String idTrechoDestino) {
		System.out.println("==> saida da chave id:"+idChave+" t:"+instanteAtual+" id.Comp:"+
				idComposicao+" tr.Dest:"+idTrechoDestino);
		/* Pre */
		assert(this.malha.getChaves().containsKey(idChave));
		assert(instanteAtual > 0);
		assert(this.malha.getComposicoes().containsKey(idComposicao));
		assert(this.malha.getTrechos().containsKey(idTrechoDestino));
		
		//Realiza a transferencia da chave para o trecho
		CChave chavee = this.malha.getChaves().get(idChave);
		CComposicao tremo = this.malha.getComposicoes().get(idComposicao);
		CTrecho trechodest = this.malha.getTrechos().get(idTrechoDestino);
		chavee.setComposicaoPassando(null);
		tremo.setTrechoChaveAtual(trechodest);
		trechodest.setnComposicoes(trechodest.getnComposicoes() + 1);
		trechodest.adicionarComposicao(tremo, true);
		if (chavee.getTrechosB().contains(idTrechoDestino)) {
			tremo.setPosicaoAtual(0f);
		} else {
			tremo.setPosicaoAtual(trechodest.getDistancia());
		}
		
		// Coloca a composicao na trensACaminho
		String proxChave = this.malha.encontrarChaveAssociada(idTrechoDestino);
		if(proxChave == null)
		{
			//Nao ha proxima chave
			CTrecho trchDestino = this.malha.getTrechos().get(idTrechoDestino);
			CComposicao trem = this.malha.getComposicoes().get(idComposicao);
			float dist = trchDestino.getDistancia();
			float fren = (float) Math.pow(trem.getVelAtual(), 2)/(2*dist);
			int temp = this.ferrovia.frearComposicao(idComposicao, 3, fren);
			this.malha.frearComposicao(idComposicao, 3, temp, fren);
		}
		else
		{
			this.malha.adicionarComposicaoACaminho(proxChave, idComposicao);
		}
		
		this.asseguraChave(idChave);		
		
		/* Configura a chave para a proxima composicao */
		String proximaComposicao = this.malha.proximaComposicaoACaminho(idChave);
		if(proximaComposicao != null)
		{			
			this.setaTrechosChave(malha.getChaves().get(idChave));
		}
		
		String chave =  malha.encontrarChaveAssociada(idTrechoDestino);
		if(chave != null){
			this.asseguraChave(chave);
			this.setaTrechosChave(malha.getChaves().get(malha.encontrarChaveAssociada(idTrechoDestino)));
		}
		
	}

	/**
	 * Sinaliza o tic gerado pelo simulador
	 * 
	 * @param instanteAtual Instante em que ocorreu o tic
	 * 
	 * @pre instanteAtual deve ser positivo --OK
	 * 
	 * @inv A quantidade de composicoes nao muda --OK
	 * 
	 * @post A malha estah atualizada para o 'instanteAtual'
	 */
	public void tic(int instanteAtual) {
		/* Pre */
		assert(instanteAtual >= 0);
		/* Inv */
		int elementCountBefore = 
			this.malha.getChaves().size()+
			this.malha.getComposicoes().size()+
			this.malha.getTrechos().size();
		int instanteAntigo = this.instanteAtual;
		
		this.instanteAtual = instanteAtual;

		if(instanteAtual == 506){
			System.out.println("gg");
		}
		
		this.malha.atualizarMalha();
		System.out.println("TIC: "+instanteAtual);
		//verificarSincroniaDEBUG();
		
		/* Inv */
		int elementCountAfter = 
			this.malha.getChaves().size()+
			this.malha.getComposicoes().size()+
			this.malha.getTrechos().size();
		assert(elementCountBefore == elementCountAfter);
		
		/* Post */
		assert(instanteAntigo + 1 == instanteAtual);
	}
	

	/**
	 * A inicializacao da Malha eh realizada em duas etapas:
	 * 	1. Todos os elementos sao instanciados na Malha
	 * 	2. Todas as referencias entre os elementos sao realizadas e seus atributos setadose
	 * 
	 * @param elementos Vetor de Strings onde cada String representa um elemento da Malha
	 * 
	 * @pre A simulacao ainda nao foi iniciada
	 * 
	 */
	public void inicializarMalha(String [] elementos) {	//acho que esse parametro tah erado - wats
		/* Pre */
		assert(this.instanteAtual < 0);
		
		/* variaveis auxiliares */
		Map<TipoAtributo, Object> atributosFixos;
		Map<TipoAtributo, Object> atributosVariaveis;
		
		/** Primeira etapa */
		for(String elem : elementos)
		{
			atributosFixos = this.ferrovia.consultaAtributosFixos(elem);
			TipoElemento tipo = (TipoElemento) atributosFixos.get(TipoAtributo.TIPO);
			switch(tipo){
			case TRECHO:
				this.malha.adicionarTrecho(elem);
				break;
			case CHAVE:
				this.malha.adicionarChave(elem);
				break;
			case COMPOSICAO:
				this.malha.adicionarComposicao(elem);
				break;
			case SENSOR:
				break;
			default: 
				System.out.println("Tipo de elemento inexistente");
			assert(false); //sempre falha 
			break;
			}
		}
		
		/** Segunda Etapa */
		/** Setta valores de Defs Glboais */
		for(String elem : elementos)
		{
			/* Fetch valores dos atributos */
			atributosFixos = this.ferrovia.consultaAtributosFixos(elem);
			atributosVariaveis = this.ferrovia.consultaEstado(elem);
			/* Set values */
			this.malha.atualizarElemento(elem, atributosFixos);
			this.malha.atualizarElemento(elem, atributosVariaveis);
		}
		
		/** Setta valores internos */
		for (CComposicao trem : this.getMalha().getComposicoes().values()){
			boolean sentido = trem.isMovendoAParaB();
			CElementoTrilho trecho = trem.getTrechoChaveAtual();
			if(sentido){
				CElementoTrilho pontab = trecho.getElementoAtualB();
				this.malha.adicionarComposicaoACaminho(pontab.getId(), trem.getId());
			}else{
				CElementoTrilho pontaa = trecho.getElementoAtualA();
				this.malha.adicionarComposicaoACaminho(pontaa.getId(), trem.getId());
			}	
			CElementoTrilho aux = trem.getTrechoChaveAtual();
			if(aux instanceof CTrecho){
				CTrecho aux2 = (CTrecho) aux;
				aux2.adicionarComposicao(trem, true);
			}
		}
		
		for (CChave chave : this.getMalha().getChaves().values())
		{
			asseguraChave(chave.getId());
			if(chave.isEmMudanca() == false){
				setaTrechosChave(chave);
			}
		}
	}
	
	/**
	 * Descobre qual o trecho subsequente a outro no itnerario de uma composicao 
	 * @param comp Composicao sobre a qual se deseja obter a informacao
	 * @param trecho Trecho depois do qual se deseja saber o proximo
	 * @return O trecho subsequente ao trecho passado como parametro no itnerario de uma composicao
	 */
	String proximoNoItinerario(CComposicao comp, String trecho){
		int i;
		String proximoTrecho = null;
		String[] itinerario = (String[]) this.ferrovia.consultaAtributo(comp.getId(), TipoAtributo.ITINERARIO);
		for(i = 0; i < itinerario.length; i++)
		{
			if(itinerario[i].equals(trecho))
			{
				proximoTrecho = itinerario[i+1];
			}
		}
		return proximoTrecho;
	}
	
	/**
	 * Muda a os trechos que uma chave liga de forma a se 
	 * adequar ao itnerario da proxima composicao a caminho dela
	 * @param chave Chave que se deseja ajustar
	 */
	void setaTrechosChave(CChave chave)
	{
		String idChave = chave.getId();
		String proximoTrecho = null;		
		
		String proximaComposicao = this.malha.proximaComposicaoACaminho(idChave);
		if(proximaComposicao != null)
		{
			CComposicao proxComp = malha.getComposicoes().get(proximaComposicao);
			String trechoOrigem = proxComp.getTrechoChaveAtual().getId();
			
			proximoTrecho = proximoNoItinerario(proxComp, trechoOrigem);
				
			/* Descobre de que lado estah origem e destino (A ou B) */
			chave = this.malha.getChaves().get(idChave);
			if(chave.getComposicaoPassando() == null){
				if(chave.getTrechosA().contains(trechoOrigem))
				{
					if(!chave.getElementoAtualA().getId().equals(trechoOrigem) ||
							!chave.getElementoAtualB().getId().equals(proximoTrecho)){
						if(this.ferrovia.mudarChave(idChave, trechoOrigem, proximoTrecho))
						{
							this.malha.mudarChave(idChave, trechoOrigem, proximoTrecho);
						}
					}
				}
				else
				{
					if(!chave.getElementoAtualA().getId().equals(proximoTrecho) ||
							!chave.getElementoAtualB().getId().equals(trechoOrigem)){
						if(this.ferrovia.mudarChave(idChave, proximoTrecho, trechoOrigem))
						{
							this.malha.mudarChave(idChave, proximoTrecho, trechoOrigem);
						}
					}
				}
			}
		}
	}

	/**
	 * Encontra a chave associada a um elemento, avalia trechos conectados a ela e as  
	 * composicoes envolvidas, toma decisoes e realiza comandos sobre o simulador a fim de
	 * evitar acidentes e guiar o trem a seu destino
	 * 
	 * @param idElemento Chave sobre a qual serao tomadas as decisoes
	 * 
	 * @pre idElemento deve existir na malha
	 * @pre idElemento deve ser a identificacao unica de um elemento da malha
	 * 
	 * @inv A quantidade de elementos na malha nao eh alterada
	 * 
	 * @post A malha recebeu comandos que a deixarao em um estado seguro
	 */
	public void asseguraChave(String idElemento) {
		//Pre
		assert(this.malha.getChaves().containsKey(idElemento) ||
				this.malha.getComposicoes().containsKey(idElemento)||
				this.malha.getTrechos().containsKey(idElemento));
		//Inv
		int elemCountBefore = 
			this.malha.getChaves().size()+
			this.malha.getComposicoes().size()+
			this.malha.getTrechos().size();

		//Code

		//A funcao trabalha em cima da chave associada ao elemento
		String chAssociada = this.malha.encontrarChaveAssociada(idElemento);
		if(chAssociada != null)
		{		
			HashMap<String, Integer> trensACaminho = this.malha.getTrensACaminho(chAssociada);
			HashMap<String, Integer> simulacao = new HashMap<String, Integer>(trensACaminho);

			//Simulacao (Acelera todos os trens ao maximo e registra o tempo que levariam para chegar aa chave)
			for(String trem : simulacao.keySet())
			{
				CComposicao comp = this.malha.getComposicoes().get(trem);
				int tempo = CControle.calculaTempoAteChave(comp, true);
				simulacao.put(comp.getId(), tempo);
			}						


			//Ordena as composicoes pelo temppo de simulacao
			List<String> mapKeys = new ArrayList<String>(simulacao.keySet());		//Id's das composicoes (desordenado)
			List<Integer> mapValues = new ArrayList<Integer>(simulacao.values()); 	//Tempos de chegada ordenados
			List<CComposicao> sortedComp = new ArrayList<CComposicao>();			//Lista das composicoes ordenadas por tempo

			Iterator<Integer> valueIt = mapValues.iterator();
			while(valueIt.hasNext())
			{
				//Tempo em ordem crescente
				int val1 = valueIt.next();

				//Busca uma composicao que tem tempo igual aa val1
				Iterator<String> keyIt = mapKeys.iterator();
				while(keyIt.hasNext())
				{
					String actual = keyIt.next();

					int val2 = simulacao.get(actual);
					if(val1 == val2)
					{
						mapKeys.remove(actual);
						sortedComp.add(this.malha.getComposicoes().get(actual));
						break;
					}
				}			
			}

			//Envia comandos para as composicoes por ordem do tempo de simulacao
			int tempoRelativo = 0, index, tempoNecess; //pode dar erro quando tem uma composicao que ainda estah passando
			int tempoMudanca = this.malha.getChaves().get(chAssociada).getTempoMudanca(); //tempo de mudanca da chave
			
			for(index = 0; index < sortedComp.size(); index++) //Pega na ordem da lista
			{
				CComposicao actualcomp = sortedComp.get(index);
				int tempoPrevisto = mapValues.get(index); //tempo previsto na simulacao 
				int tempo = Math.max(tempoPrevisto, tempoRelativo);  //Pega o maior dos tempos (primeira vez eh sempre tempoPrevisto)

				//aceleracao para chegar no tempo Previsto (primeira composicao eh sempre acelMax) 
				float accel = CControle.calculaAceleracaoNoTempo(actualcomp, tempo); 
				float velocidade = calculaVelocidadeNoTempo(actualcomp, tempo, accel); //velocidade da comp no final do trecho 
				
				//tempo de travessia da comp na chave
				int tempoTravessia = (int)Math.ceil((double)this.malha.getChaves().get(chAssociada).getDistancia() / velocidade);
				
				//-- Verificar se alguma outra composicao quer entrar no mesmo trecho destino --
				boolean me = true, order = false;
				String idTrechoDestino = proximoNoItinerario(actualcomp, actualcomp.getTrechoChaveAtual().getId());
				if(idTrechoDestino != null)
				{
					CTrecho CTrechoDestino = this.malha.getTrechos().get(idTrechoDestino);

					//Verifica de que lado estah o trecho
					CChave x;
					if(this.malha.getChaves().get(chAssociada).getTrechosA().contains(idTrechoDestino)){
						x = (CChave)CTrechoDestino.getElementoAtualA();
					}else{
						x = (CChave)CTrechoDestino.getElementoAtualB();
					}
					if(x != null){
						//Encontra a composicao que quer entrar no  mesmo trecho
						for(CComposicao victim : x.getTrensACaminho().keySet()){
							String victimProx = proximoNoItinerario(victim, victim.getTrechoChaveAtual().getId());
							if(idTrechoDestino.equals(victimProx)){
								
								//verifica tempo de chegada em suas respectivas chaves
								if(CControle.calculaTempoAteChave(actualcomp, true) > CControle.calculaTempoAteChave(victim, true))
								{
									//Esta composicao chega depois aa sua chave
									me = false;
									order = true;
									if(!CControle.floatEquals(actualcomp.getVelAtual(), 2)){
										if(actualcomp.getVelAtual() > 2){
											int tempoN = this.ferrovia.frearComposicao(actualcomp.getId(), 2, actualcomp.getFrenMax());
											this.malha.frearComposicao(actualcomp.getId(), 2, tempoN, actualcomp.getFrenMax());
										}else if (actualcomp.getAcelAtual() < 2){
											int tempoN = this.ferrovia.acelerarComposicao(actualcomp.getId(), 2, 0.01f);
											this.malha.acelerarComposicao(actualcomp.getId(), 2, tempoN, 0.01f);
										}
									}
								}
								else
								{
									//A outra composicao chega depois aa sua chave
									if(!CControle.floatEquals(victim.getVelAtual(), 2)){
										if((victim.getVelAtual() > 2)  && (victim.getFrenAtual() < victim.getFrenMax())){
											int tempoN = this.ferrovia.frearComposicao(victim.getId(), 2, victim.getFrenMax());
											this.malha.frearComposicao(victim.getId(), 2, tempoN, victim.getFrenMax());
										}else if (victim.getVelAtual() < 2){
											int tempoN = this.ferrovia.acelerarComposicao(victim.getId(), 2, 0.01f);
											this.malha.acelerarComposicao(victim.getId(), 2, tempoN, 0.01f);
										}
									}
								}
							}			
						}
					}
				}
				//-- ** --
				
				// -- Verifica se alguma composicao quer entrar no trecho atual --
				CChave chave1 = this.malha.getChaves().get(this.malha.encontrarChaveAssociada(actualcomp.getId()));
				if(chave1 != null && !order && me){
					for(CComposicao victim : chave1.getTrensACaminho().keySet()){
						String victimProx = proximoNoItinerario(victim, victim.getTrechoChaveAtual().getId());
						String actualProx = proximoNoItinerario(actualcomp, actualcomp.getTrechoChaveAtual().getId());
						
						if(actualcomp.getTrechoChaveAtual().getId().equals(victimProx) && victim.getTrechoChaveAtual().getId().equals(actualProx)){
							this.theBigRedButton();
							me = false;
							order = true;
						}
						else if(actualcomp.getTrechoChaveAtual().getId().equals(victimProx) || victim.getTrechoChaveAtual().getId().equals(actualProx)){

							//verifica tempo de chegada em suas respectivas chaves
							if(CControle.calculaTempoAteChave(actualcomp, true) > CControle.calculaTempoAteChave(victim, true))
							{
								//Esta composicao chega depois aa sua chave
								me = false;
								order = true;
								if(!CControle.floatEquals(actualcomp.getVelAtual(), 2)){
									if(actualcomp.getVelAtual() > 2){
										int tempoN = this.ferrovia.frearComposicao(actualcomp.getId(), 2, actualcomp.getFrenMax());
										this.malha.frearComposicao(actualcomp.getId(), 2, tempoN, actualcomp.getFrenMax());
									}else if (actualcomp.getAcelAtual() < 2){
										int tempoN = this.ferrovia.acelerarComposicao(actualcomp.getId(), 2, 0.01f);
										this.malha.acelerarComposicao(actualcomp.getId(), 2, tempoN, 0.01f);
									}
								}
							}
							else
							{
								//A outra composicao chega depois aa sua chave
								if(!CControle.floatEquals(victim.getVelAtual(), 2)){
									if((victim.getVelAtual() > 2) ){
										int tempoN = this.ferrovia.frearComposicao(victim.getId(), 2, victim.getFrenMax());
										this.malha.frearComposicao(victim.getId(), 2, tempoN, victim.getFrenMax());
									}else if (victim.getVelAtual() < 2){
										int tempoN = this.ferrovia.acelerarComposicao(victim.getId(), 2, 0.01f);
										this.malha.acelerarComposicao(victim.getId(), 2, tempoN, 0.01f);
									}
								}
							}
						}			
					}
				}
				// -- ** ---
				
				// -- Nenhuma composicao quer entrar no mesmo trecho de destino ou no trecho atual --
				if(me && !order)
				{					
					//Aceleracao positiva == acelerar
					//Aceleracao negativa == frear
					if(!CControle.floatEquals(velocidade, actualcomp.getVelAtual())){
						if(accel > 0){
							tempoNecess =  this.ferrovia.acelerarComposicao(actualcomp.getId(), velocidade, accel);
							int tempoReal = (int)Math.ceil((velocidade - actualcomp.getVelAtual())/accel);
							this.malha.acelerarComposicao(actualcomp.getId(), velocidade, tempoReal, accel);
						}else{
							tempoNecess = this.ferrovia.frearComposicao(actualcomp.getId(), velocidade, Math.abs(accel));
							int tempoReal = (int)Math.ceil((actualcomp.getVelAtual() - velocidade)/Math.abs(accel));
							this.malha.frearComposicao(actualcomp.getId(), velocidade, tempoReal, Math.abs(accel));
						}
					}
					tempoRelativo += tempo + tempoTravessia + tempoMudanca + SEC_MARGIN;
				}
				// -- ** --
			}
			
			//Atualizacao de trensACaminho de chave
			CChave chave = malha.getChaves().get(chAssociada);
			for(String trem : trensACaminho.keySet())
			{
				CComposicao comp = this.malha.getComposicoes().get(trem);
				int tempo = CControle.calculaTempoAteChave(comp, false);
				chave.getTrensACaminho().put(comp, tempo);
			}	
			
			//Inv
			int elemCountAfter = 
				this.malha.getChaves().size()+
				this.malha.getComposicoes().size()+
				this.malha.getTrechos().size();
			assert(elemCountBefore == elemCountAfter);
		}
		
	}


	/**
	 * Instante referente aos eventos de tic
	 * @return O instante da simulacao em TICs
	 */
	public int getInstanteAtual() {
		return instanteAtual;
	}

	/**
	 * Seta o valor do instante atual da simulacao
	 * @param instanteAtual Valor atual do TIC
	 */
	public void setInstanteAtual(int instanteAtual) {
		this.instanteAtual = instanteAtual;
	}

	/**
	 * Verifica se o estado dos elementos de malha estao sincronizados com os elementos do simulador
	 * 
	 * @return Retorna 1 para verdadeiro, 0 para falso
	 * 
	 * @inv Os atributos dos elementos da malha nao sao alterados
	 * @inv A quantidade de elementos na malha na eh alterada --OK
	 * 
	 * @post Retorna 1 quando os atributos estao sincronizados e 0 caso contrario
	 */
	private int verificarSincroniaDEBUG() {
		int ok = 1;
		Map<TipoAtributo, Object> atributosVariaveis;
		
		/* Inv */
		int elemCountBefore = 
			this.malha.getChaves().size()+
			this.malha.getComposicoes().size()+
			this.malha.getTrechos().size();
		
		/* Para cada elemento */
		String[] elementos = this.ferrovia.consultaElementos();
		for(String elem : elementos)
		{
			atributosVariaveis = this.ferrovia.consultaEstado(elem);
			/* Descobre o tipo do elemento */
			TipoElemento tipo = (TipoElemento) this.ferrovia.consultaAtributo(elem, TipoAtributo.TIPO);
			
			/* Verifica todos os atributos variaveis */
			switch(tipo){
			case CHAVE:
				CChave ichave = this.malha.getChaves().get(elem);
				for(TipoAtributo attrChave : atributosVariaveis.keySet())
				{
					switch(attrChave){
					case EM_MUDANCA:
						//if(ichave.isEmMudanca() != (Boolean) atributosVariaveis.get(TipoAtributo.EM_MUDANCA)) assert(false);
						break;
					case TEMPO_CONCLUSAO:
						//System.out.println("Tempo Conclusao: "+ichave.getTempoConclusao()+"||"+ (Integer) atributosVariaveis.get(TipoAtributo.TEMPO_CONCLUSAO));
						//if(ichave.getTempoConclusao() != (Integer) atributosVariaveis.get(TipoAtributo.TEMPO_CONCLUSAO)) assert(false);
						break;
					case ID_TR_CH_A:
						if(ichave.getElementoAtualA().getId() != (String) atributosVariaveis.get(TipoAtributo.ID_TR_CH_A)) assert(false);
						break;
					case ID_TR_CH_B:
						if(ichave.getElementoAtualB().getId() != (String) atributosVariaveis.get(TipoAtributo.ID_TR_CH_B)) assert(false);
						break;
					}
				}
				break;
			case COMPOSICAO:
				CComposicao itrem = this.malha.getComposicoes().get(elem);
				if(itrem != null){
					for(TipoAtributo attrTrem : atributosVariaveis.keySet())
					{
						switch(attrTrem){
						case ID_TR_CH_ATUAL:
							if(itrem.getTrechoChaveAtual().getId() != (String) atributosVariaveis.get(TipoAtributo.ID_TR_CH_ATUAL)) assert(false);
							break;
						case POSICAO_ATUAL:
							System.out.println("COMP POS Atual: " + itrem.getPosicaoAtual() + "||" + (Float) atributosVariaveis.get(TipoAtributo.POSICAO_ATUAL));
							if(!CControle.floatEqualsDist(itrem.getPosicaoAtual(),(Float) atributosVariaveis.get(TipoAtributo.POSICAO_ATUAL))) assert(false);
							break;
						case VEL_ATUAL:
							System.out.println("Vel Atual " + itrem.getVelAtual() + "||"+ (Float) atributosVariaveis.get(TipoAtributo.VEL_ATUAL));
							if(!CControle.floatEquals(itrem.getVelAtual(), (Float) atributosVariaveis.get(TipoAtributo.VEL_ATUAL))) assert(false);
							break;
						case SENTIDO_ATUAL:
							if(itrem.isMovendoAParaB() != (Boolean) atributosVariaveis.get(TipoAtributo.SENTIDO_ATUAL)) assert(false);
							break;
						case ACEL_ATUAL:
							//System.out.println("TC: "+itrem.getTempoConclusao() +"# +Acel "+itrem.getAcelAtual()+"||"+(Float) atributosVariaveis.get(TipoAtributo.ACEL_ATUAL));
							if(!CControle.floatEquals(itrem.getAcelAtual(),(Float) atributosVariaveis.get(TipoAtributo.ACEL_ATUAL))) assert(false);
							break;
						case FREN_ATUAL:
							if(!CControle.floatEquals(itrem.getFrenAtual(), (Float) atributosVariaveis.get(TipoAtributo.FREN_ATUAL))) assert(false);
							break;
						case EM_MOVIMENTO:
							if(itrem.isEmMovimento() != (Boolean) atributosVariaveis.get(TipoAtributo.EM_MOVIMENTO)) assert(false);
							break;
						}
					}
				}
				break;
			case TRECHO:
				CTrecho itrecho = this.malha.getTrechos().get(elem);
				for(TipoAtributo attrTrecho : atributosVariaveis.keySet())
				{
					switch(attrTrecho){
					case N_COMPOSICOES:
						if(itrecho.getnComposicoes() != (Integer) atributosVariaveis.get(TipoAtributo.N_COMPOSICOES)) assert(false);
						break;
					}
				}
				break;
			}
							
		}
		
		/* Inv */
		int elemCountAfter = 
			this.malha.getChaves().size()+
			this.malha.getComposicoes().size()+
			this.malha.getTrechos().size();
		assert(elemCountAfter == elemCountBefore);
		
		return ok;
	}

	
	/**
	 * Malha representando a malha real do simulador
	 * 
	 * @return Retorna a malha do controle
	 */
	public CMalha getMalha() {
		return malha;
	}

	
	/**
	 * Freia todas as composicoes a zero
	 */
	public void theBigRedButton() {
		for (String id : this.malha.getComposicoes().keySet()) {
			if (this.malha.getComposicoes().get(id).getVelAtual() > 0) {
				int tempo = this.ferrovia.frearComposicao(id, 0f, this.malha.getComposicoes().get(id).getFrenMax());
				this.malha.frearComposicao(id, 0f, tempo, this.malha.getComposicoes().get(id).getFrenMax());
			}
		}
		//System.out.println("THE BIG REB BUTTON!!!!");
		throw new RuntimeException("The Big Red Button! Colisao iminente detectada...");
	}


}
